package com.sayice.robotconflict.viewmodel

import android.app.Application
import android.util.Log
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope
import com.google.gson.JsonObject
import com.sayice.robotconflict.Config
import com.sayice.robotconflict.Game
import com.sayice.robotconflict.model.Message
import com.sayice.robotconflict.model.property.Coord
import com.sayice.robotconflict.model.receive.*
import com.sayice.robotconflict.ui.screen.GAME_DESIGN_HEIGHT
import com.sayice.robotconflict.ui.screen.GAME_DESIGN_WIDTH
import com.sayice.robotconflict.util.GSON
import com.sayice.robotconflict.view.route.Route
import io.netty.bootstrap.Bootstrap
import io.netty.channel.Channel
import io.netty.channel.ChannelHandlerContext
import io.netty.channel.ChannelInboundHandlerAdapter
import io.netty.channel.ChannelInitializer
import io.netty.channel.nio.NioEventLoopGroup
import io.netty.channel.socket.SocketChannel
import io.netty.channel.socket.nio.NioSocketChannel
import io.netty.handler.codec.LengthFieldBasedFrameDecoder
import io.netty.handler.codec.LengthFieldPrepender
import io.netty.handler.codec.string.StringDecoder
import io.netty.handler.codec.string.StringEncoder
import io.netty.handler.timeout.IdleState
import io.netty.handler.timeout.IdleStateEvent
import io.netty.handler.timeout.IdleStateHandler
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.Response
import java.io.IOException
import java.util.concurrent.TimeUnit
import kotlin.concurrent.thread

class GameViewModel(application: Application) : AndroidViewModel(application) {
    /**
     * Toast信息
     */
    private val _toastFlow = MutableSharedFlow<String>()
    val toastFlow = _toastFlow.asSharedFlow()

    /**
     * 页面
     */
    private val _pageFlow = MutableStateFlow(Route.LOGIN)
    val pageFlow = _pageFlow.asStateFlow()

    /**
     * 排行榜
     */
    private val _rankingFlow = MutableStateFlow<List<PlayerInfo>>(emptyList())
    val rankingFlow = _rankingFlow.asStateFlow()

    /**
     * 玩家信息
     */
    private val _playerInfoFlow = MutableStateFlow<PlayerInfo?>(null)
    val playerInfoFlow = _playerInfoFlow.asStateFlow()

    /**
     * 好友列表
     */
    private val _friendsFlow = MutableStateFlow<List<FriendInfo>>(emptyList())
    val friendsFlow = _friendsFlow.asStateFlow()

    /**
     * 玩家搜索结果
     */
    private val _searchedPlayersFlow = MutableStateFlow<List<FriendInfo>>(emptyList())
    val searchedPlayersFlow = _searchedPlayersFlow.asStateFlow()

    /**
     * 好友申请
     */
    private val _friendReqsFlow = MutableStateFlow<List<FriendReqEntity>>(emptyList())
    val friendReqsFlow = _friendReqsFlow.asStateFlow()

    /**
     * 组队邀请
     */
    private val _teamInviteInfoFlow = MutableStateFlow<TeamInviteInfo?>(null)
    val teamInviteInfoFlow = _teamInviteInfoFlow.asStateFlow()

    /**
     * 组队玩家信息
     */
    private val _teamPlayersInfoFlow = MutableStateFlow<List<TeamPlayerInfo?>>(emptyList())
    val teamPlayersInfoFlow = _teamPlayersInfoFlow.asStateFlow()

    /**
     * 对局帧数据
     */
    private val _gameFrameDataFlow = MutableSharedFlow<GameFrameData>()
    val gameFrameDataFlow = _gameFrameDataFlow.asSharedFlow()

    /**
     * 投降玩家数量
     */
    private val _surrenderedCountFlow = MutableStateFlow(0)
    val surrenderedCountFlow = _surrenderedCountFlow.asStateFlow()

    /**
     * 登录信息
     */
    @Volatile
    lateinit var loginInfo: LoginInfo

    /**
     * 对局中的队伍下标
     */
    @Volatile
    var teamIndex = 0

    /**
     * 对局中的对手队伍下标
     */
    val rivalTeamIndex
        get() = teamIndex xor 1

    /**
     * 能量塔坐标
     */
    @Volatile
    lateinit var towersCoord: Array<Coord>

    /**
     * 登录 token
     */
    private var token: String = ""

    private val httpClient = OkHttpClient.Builder().connectTimeout(10, TimeUnit.SECONDS).build()

    /**
     * 与服务端通信的 Channel
     */
    private lateinit var channel: Channel

    /**
     * 显示提示信息
     *
     * @param text 内容
     */
    fun showToast(text: String) {
        viewModelScope.launch {
            _toastFlow.emit(text)
        }
    }

    /**
     * 跳转到页面
     *
     * @param page 页面
     */
    fun goToPage(page: Route) {
        _pageFlow.value = page
    }

    /**
     * 清除玩家搜索结果
     */
    fun clearSearchedPlayers() {
        _searchedPlayersFlow.value = emptyList()
    }

    /**
     * 清除组队邀请
     */
    fun clearTeamInviteInfo() {
        _teamInviteInfoFlow.value = null
    }

    // TODO 加注释
    fun xOf(num: Int) = if (teamIndex == 0) num else GAME_DESIGN_WIDTH - num - Game.ROBOT_SIZE

    fun xOf(num: Double) = if (teamIndex == 0) num else GAME_DESIGN_WIDTH - num - Game.ROBOT_SIZE

    fun yOf(num: Int) = if (teamIndex == 0) num else GAME_DESIGN_HEIGHT - num - Game.ROBOT_SIZE

    fun yOf(num: Double) = if (teamIndex == 0) num else GAME_DESIGN_HEIGHT - num - Game.ROBOT_SIZE

    fun directionOf(value: Int) = if (teamIndex == 0) value else -value

    /**
     * 注册
     *
     * @param name 玩家名
     * @param password 密码
     */
    fun register(name: String, password: String) {
        val url = "${Config.LOGIN_SERVER_URL}/player/register"
        val params = GSON.toJson(mapOf("name" to name, "password" to password))
        val reqBody = params.toRequestBody("application/json; charset=utf-8".toMediaType())
        val req = Request.Builder().url(url).post(reqBody).build()
        val resp: Response
        try {
            resp = httpClient.newCall(req).execute()
        } catch (e: IOException) {
            e.printStackTrace()
            showToast("无法连接服务器，请检查网络！")
            return
        }
        val result = GSON.fromJson(resp.body!!.string(), JsonObject::class.java)
        if (result.getAsJsonPrimitive("code").asInt != 200) {
            showToast(result.getAsJsonPrimitive("msg").asString)
            return
        }
        showToast("注册成功！")
    }

    /**
     * 登录
     *
     * @param name 玩家名
     * @param password 密码
     */
    fun login(name: String, password: String) {
        val url = "${Config.LOGIN_SERVER_URL}/player/login"
        val params = GSON.toJson(mapOf("name" to name, "password" to password))
        val reqBody = params.toRequestBody("application/json; charset=utf-8".toMediaType())
        val req = Request.Builder().url(url).post(reqBody).build()
        val resp: Response
        try {
            resp = httpClient.newCall(req).execute()
        } catch (e: IOException) {
            e.printStackTrace()
            showToast("无法连接服务器，请检查网络！")
            return
        }
        val result = GSON.fromJson(resp.body!!.string(), JsonObject::class.java)
        if (result.getAsJsonPrimitive("code").asInt != 200) {
            showToast(result.getAsJsonPrimitive("msg").asString)
            return
        }
        token = result.getAsJsonPrimitive("data").asString
        // 连接服务器
        connect()
    }

    /**
     * 向服务器发送消息
     *
     * @param msg 消息
     */
    fun sendToServer(msg: Message) {
        channel.writeAndFlush(msg.toJson())
    }

    /**
     * 断开与服务器的连接
     */
    fun disConnect() {
        if (this::channel.isInitialized) {
            channel.close()
        }
    }

    /**
     * 连接服务器
     */
    private fun connect() {
        val eventLoopGroup = NioEventLoopGroup()
        val bootstrap = Bootstrap().group(eventLoopGroup)
            .channel(NioSocketChannel::class.java)
            .handler(object : ChannelInitializer<SocketChannel>() {
                override fun initChannel(channel: SocketChannel) {
                    channel.pipeline()
                        .addLast(LengthFieldBasedFrameDecoder(Int.MAX_VALUE, 0, 2, 0, 2))
                        .addLast(StringDecoder())
                        .addLast(IdleStateHandler(15, 5, 0))
                        .addLast(object : ChannelInboundHandlerAdapter() {
                            // TODO 如果服务端write(null)会怎么样？msg是否要改为可空类型？
                            override fun channelRead(ctx: ChannelHandlerContext, msg: Any) {
                                val message = GSON.fromJson(msg as String, Message::class.java)
                                handleMessage(message)
                            }

                            override fun userEventTriggered(
                                ctx: ChannelHandlerContext,
                                evt: Any?
                            ) {
                                // 心跳检测
                                if (evt is IdleStateEvent) {
                                    if (evt.state() == IdleState.READER_IDLE) {
                                        Log.i("GameViewModel", "长时间没有收到服务端的消息，channel关闭")
                                        ctx.channel().close()
                                    } else if (evt.state() == IdleState.WRITER_IDLE) {
                                        ctx.channel()
                                            .writeAndFlush(Message(type = "heartbeat").toJson())
                                    }
                                } else {
                                    ctx.fireUserEventTriggered(evt);
                                }
                            }
                        })
                        .addLast(LengthFieldPrepender(2))
                        .addLast(StringEncoder())
                }
            })
        val channelFuture = bootstrap.connect(Config.GAME_SERVER_HOST, Config.GAME_SERVER_PORT)
        channelFuture.addListener {
            if (it.isSuccess) {
                channel = channelFuture.channel()
                sendToServer(Message(type = "connect", data = token))
                // 循环检测 channel 是否关闭
                thread {
                    while (channel.isOpen) {
                        Thread.sleep(2000)
                    }
                    _pageFlow.value = Route.LOGIN
                    showToast("网络出问题了！")
                }
            } else {
                it.cause().printStackTrace()
                showToast("无法连接服务器，请检查网络！")
            }
        }
    }

    /**
     * 处理来自服务器的消息
     *
     * @param message 消息
     */
    private fun handleMessage(message: Message) {
        when (message.type) {
            "msg" -> {
                showToast(message.data as String)
            }
            "action" -> {
                when (message.action) {
                    "enterHall" -> {
                        loginInfo = message.dataAsBean()
                        _pageFlow.value = Route.HALL
                    }
                    "enterMatch" -> {
                        _pageFlow.value = Route.MATCH
                    }
                    "stopMatch" -> {
                        _pageFlow.value = Route.HALL
                    }
                    "win", "lose", "tie" -> {
                        showToast(message.action)
                        _pageFlow.value = Route.HALL
                    }
                }
            }
            "player" -> {
                when (message.action) {
                    "ranking" -> {
                        _rankingFlow.value = message.dataAsBeanList()
                    }
                    "info" -> {
                        _playerInfoFlow.value = message.dataAsBean()
                    }
                }
            }
            "social" -> {
                when (message.action) {
                    "friends" -> {
                        _friendsFlow.value = message.dataAsBeanList()
                    }
                    "players" -> {
                        _searchedPlayersFlow.value = message.dataAsBeanList()
                    }
                    "friendReqs" -> {
                        _friendReqsFlow.value = message.dataAsBeanList()
                    }
                }
            }
            "team" -> {
                when (message.action) {
                    "invite" -> {
                        _teamInviteInfoFlow.value = message.dataAsBean()
                    }
                    "players" -> {
                        _teamPlayersInfoFlow.value = message.dataAsBeanList()
                        // 通过房间号加入房间时需要在这里进行页面跳转
                        if (_pageFlow.value != Route.TEAM_ROOM) {
                            _pageFlow.value = Route.TEAM_ROOM
                        }
                    }
                }
            }
            "game_info" -> {
                val gameInfo = message.dataAsBean<GameInfo>()
                teamIndex = gameInfo.teamIndex
                towersCoord = gameInfo.towersCoord
                _pageFlow.value = Route.PLAY
            }
            "frame_data" -> {
                viewModelScope.launch {
                    _gameFrameDataFlow.emit(message.dataAsBean())
                }
            }
            "game" -> {
                when (message.action) {
                    "surrenderedCount" -> {
                        _surrenderedCountFlow.value = (message.data as Number).toInt()
                    }
                }
            }
        }
    }
}